// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <memory>
#include <turbo/base/macros.h>

namespace nebula::util {

    /// A wrapper around std::mutex since we can't use it directly in
    /// public headers due to C++/CLI.
    /// https://docs.microsoft.com/en-us/cpp/standard-library/mutex#remarks
    class TURBO_EXPORT Mutex {
    public:
        Mutex();

        Mutex(Mutex &&) = default;

        Mutex &operator=(Mutex &&) = default;

        /// A Guard is falsy if a lock could not be acquired.
        class TURBO_EXPORT Guard {
        public:
            Guard() : locked_(nullptr, [](Mutex *mutex) {}) {}

            Guard(Guard &&) = default;

            Guard &operator=(Guard &&) = default;

            explicit operator bool() const { return bool(locked_); }

            void Unlock() { locked_.reset(); }

        private:
            explicit Guard(Mutex *locked);

            std::unique_ptr<Mutex, void (*)(Mutex *)> locked_;
            friend Mutex;
        };

        Guard TryLock();

        Guard Lock();

    private:
        struct Impl;
        std::unique_ptr<Impl, void (*)(Impl *)> impl_;
    };

#ifndef _WIN32
    /// Return a pointer to a process-wide, process-specific Mutex that can be used
    /// at any point in a child process.  NULL is returned when called in the parent.
    ///
    /// The rule is to first check that getpid() corresponds to the parent process pid
    /// and, if not, call this function to lock any after-fork reinitialization code.
    /// Like this:
    ///
    ///   std::atomic<pid_t> pid{getpid()};
    ///   ...
    ///   if (pid.load() != getpid()) {
    ///     // In child process
    ///     auto lock = GlobalForkSafeMutex()->Lock();
    ///     if (pid.load() != getpid()) {
    ///       // Reinitialize internal structures after fork
    ///       ...
    ///       pid.store(getpid());
    TURBO_EXPORT
    Mutex *GlobalForkSafeMutex();

#endif

}  // namespace nebula::util
